import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { Request, Response } from 'express';
import { Observable } from 'rxjs';

interface JwtUserData {
  userId: number;
  username: string;
}

declare module 'express' {
  interface Request {
    user: JwtUserData
  }
}

@Injectable()
export class AuthGuard implements CanActivate {

  @Inject()
  private reflector: Reflector;

  @Inject(JwtService)
  private jwtService: JwtService;

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();
    const response: Response = context.switchToHttp().getResponse();

    const requireLogin = this.reflector.getAllAndOverride('require-login', [
      context.getClass(),
      context.getHandler()
    ]);

    if (!requireLogin) {
      return true;
    }

    const authorization = request.headers.authorization;

    if (!authorization) {
      throw new UnauthorizedException('用户未登录');
    }

    try {
      const parts = authorization.split(' ');
      if (parts.length !== 2 || parts[0] !== 'Bearer') {
        throw new UnauthorizedException('无效的授权头');
      }
      const token = parts[1];

      const data = this.jwtService.verify<JwtUserData>(token);

      request.user = {
        userId: data.userId,
        username: data.username,
      }

      response.header('token', this.jwtService.sign({
        userId: data.userId,
        username: data.username
      }, {
        expiresIn: '7d'
      }))

      return true;
    } catch (e) {
      console.error('Token verification failed:', e);
      throw new UnauthorizedException('token 失效，请重新登录');
    }
  }
}

/**
 * 8.7日
 * 添加了 /user/login 接口来实现登录，登录后返回 jwt token。
 * 访问的时候在 Authorization 的 header 带上 jwt 的 token 就能通过 AuthGuard 的鉴权。
 * 完成token 的自动续期，也就是访问接口后在 header 返回新 token。
 * 然后封装了 @RequireLogin 和 @UserInfo 两个自定义装饰器。
 * 登录之后，就可以访问一些需要 user 信息的接口了。
 */